/*
 * Copyright (C) 2008-2010 Oliver Horn.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.funcj.data;

import java.io.Serializable;
import java.util.Map;
import java.util.NoSuchElementException;

import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Objects;
import com.googlecode.funcj.Transformable;

/**
 * A value with two possibilities.
 *
 * An {@code Either} object is either a {@link Left Left} object (which
 * encapsulates a value of type {@code L}) or a {@link Right Right} object
 * (which encapsulates a value of type {@code R}).
 *
 * <p>
 * A common use case is error handling. By convention, the result of a
 * successful computation is stored in a {@code Right} object whereas a failure
 * results in an appropriate {@code Left} object.
 *
 * @param <L> the type of the left value
 * @param <R> the type of the right value
 *
 * @see Eithers
 *
 * @author Oliver Horn
 */
@Immutable
public abstract class Either<L, R> implements Transformable<R>, Serializable {

	private static final long serialVersionUID = -4829044965320507229L;

	private Either() {
	}

	/**
	 * Returns whether this object is a {@link Left}.
	 * @return {@code true} if this object is a {@link Left}, {@code} false otherwise
	 */
	public boolean isLeft() {
		return false;
	}

    /**
     * Returns whether this object is a {@link Right}.
	 * @return {@code true} if this object is a {@link Right}, {@code} false otherwise
     */
	public boolean isRight() {
		return false;
	}

	/**
	 * Gets the value of a {@link Left}.
	 * @throws NoSuchElementException if this object is not a {@link Left}
	 * @return the encapsulated {@code Left} value
	 */
	public L left() {
		throw new NoSuchElementException("Left is empty");
	}

	/**
	 * Gets the value of a {@link Right}.
	 * @throws NoSuchElementException if this object is not a {@link Right}
	 * @return the encapsulated {@code Right} value
	 */
	public R right() {
		throw new NoSuchElementException("Right is empty");
	}

	/**
	 * Gets the {@code Left} value as {@link Maybe} object.
	 * If this object is a {@code Left}, the encapsulated value is returned
	 * as {@link Maybe.Just}. Otherwise {@link Maybe.Nothing} is returned.
	 */
	public final Maybe<L> leftMaybe() {
		return isLeft() ? new Maybe.Just<L>(left()) : Maybes.<L>nothing();
	}

	/**
	 * Gets the {@code Right} value as {@link Maybe} object.
	 * If this object is a {@code Right}, the encapsulated value is returned
	 * as {@link Maybe.Just}. Otherwise {@link Maybe.Nothing} is returned.
	 */
	public final Maybe<R> rightMaybe() {
		return isRight() ? new Maybe.Just<R>(right()) : Maybes.<R>nothing();
	}

	/**
	 * Depending on whether this object is a {@code Left} or a {@code Right} object, feeds
	 * one of the specified functions with the value of this object.
	 *
	 * @param leftFunction the function to use when this object is a {@code Left}
	 * @param rightFunction the function to use when this object is a {@code Right}
	 * @return the value returned by the function
	 */
	public abstract <V> V either(Function<? super L, ? extends V> leftFunction, Function<? super R, ? extends V> rightFunction);

	/**
	 * Depending on whether this object is a {@code Left} or a {@code Right} object, performs
	 * a lookup against one of the specified maps.

	 * @param leftMap the map to use when this object is a {@code Left}
	 * @param rightMap the map to use when this object is a {@code Right}
	 * @return the value returned by the map lookup
	 */
	public final <V> V either(Map<? super L, ? extends V> leftMap, Map<? super R, ? extends V> rightMap) {
		return either(Functions.forMap(leftMap), Functions.forMap(rightMap));
	}

	/**
	 * Returns a new {@code Either} where the {@code Right} value (if any) is
	 * transformed using the given function.
	 * If this object is a {@code Left}, the returned {@code Either} object
	 * is unchanged.
	 */
	public abstract <V> Either<L, V> transform(Function<? super R, V> function);

    /**
     * Returns a new {@code Either} object with swapped elements.
     *
     * <p>Swapping an {@code Either} twice will return an {@code Either} which is equal to the original one.
     * @return the swapped {@code Either} object
     */
	public abstract Either<R, L> swap();

	/**
	 * {@inheritDoc}
	 */
	@Override
	public abstract int hashCode();

	/**
	 * {@inheritDoc}
	 */
	@Override
	public abstract boolean equals(Object obj);

	/**
	 * {@inheritDoc}
	 */
	@Override
	public abstract String toString();

	/**
	 * {@code Left} is one possible option of {@code Either}.
	 *
	 * @see Either
	 */
	@Immutable
	public static final class Left<L, R> extends Either<L, R> {

		private static final long serialVersionUID = -9067237872295660333L;

		private final L value;

		/**
		 * Constructs a new {@code Left} object.
		 * @param value the value of this {@code Left} object
		 */
		// TODO: @ConstructorProperties("value")
		public Left(@Nullable L value) {
			this.value = value;
		}

		/**
		 * Returns the value of this {@code Left} object.
		 * @return the value of this {@code Left} object
		 */
		public L getValue() {
			return value;
		}

		@Override
		public boolean isLeft() {
			return true;
		}

		@Override
		public L left() {
		    return value;
		}

		@Override
		public Either<R, L> swap() {
			return new Right<R, L>(value);
		}

		@SuppressWarnings("unchecked")
		@Override
		public <V> Either<L, V> transform(Function<? super R, V> function) {
			return (Either<L, V>) this;
		}

		@Override
		public <V> V either(Function<? super L, ? extends V> leftFunction, Function<? super R, ? extends V> rightFunction) {
			return leftFunction.apply(value);
		}

		@Override
		public int hashCode() {
			return (value != null) ? 3 * value.hashCode() : 0;
		}

		@Override
		public boolean equals(Object obj) {
			return (obj instanceof Left) && Objects.equal(value, ((Left<?, ?>) obj).value);
		}

		@Override
		public String toString() {
			return "Either.Left[" + value + "]";
		}
	}

	/**
	 * {@code Right} is another possible option of {@code Either}.
	 *
	 * @see Either
	 */
	@Immutable
	public static final class Right<L, R> extends Either<L, R> {

		private static final long serialVersionUID = -4743970628713111421L;

		private final R value;

		/**
		 * Constructs a new {@code Right} object.
		 * @param value the value of this {@code Right} object
		 */
		// TODO: @ConstructorProperties("value")
		public Right(@Nullable R value) {
			this.value = value;
		}

		/**
		 * Returns the value of this {@link Right} object.
		 * @return the value of this {@link Right} object
		 */
		public R getValue() {
			return value;
		}

		@Override
		public boolean isRight() {
		    return true;
		}

		@Override
		public R right() {
		    return value;
		}

		@Override
		public Either<R, L> swap() {
			return new Left<R, L>(value);
		}

		@Override
		public <V> Either<L, V> transform(Function<? super R, V> function) {
			return new Right<L, V>(function.apply(value));
		}

		@Override
		public <V> V either(Function<? super L, ? extends V> leftFunction, Function<? super R, ? extends V> rightFunction) {
			return rightFunction.apply(value);
		}

		@Override
		public int hashCode() {
			return (value != null) ? 7 * value.hashCode() : 0;
		}

		@Override
		public boolean equals(Object obj) {
			return (obj instanceof Right) && Objects.equal(value, ((Right<?, ?>) obj).value);
		}

		@Override
		public String toString() {
			return "Either.Right[" + value + "]";
		}
	}
}
